/**********************************************************************

  Audacity: A Digital Audio Editor

  VSTEffect.cpp

  Dominic Mazzoni

  Paul Licameli split from VSTEffect.cpp

  This class implements a VST Plug-in effect.  The plug-in must be
  loaded in a platform-specific way and passed into the constructor,
  but from here this class handles the interfacing.

********************************************************************//**

\class AEffect
\brief VST Effects class, conforming to VST layout.

*//********************************************************************/

#include "VSTEffectBase.h"
#include "VSTInstance.h"

#include <wx/time.h>

#include "Base64.h"
#include "ConfigInterface.h"

VSTEffectBase::VSTEffectBase(const PluginPath& path)
    :  VSTWrapper(path)
{
    memset(&mTimeInfo, 0, sizeof(mTimeInfo));
    mTimeInfo.samplePos = 0.0;
    mTimeInfo.sampleRate = 44100.0; // this is a bogus value, but it's only for the display
    mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
    mTimeInfo.tempo = 120.0;
    mTimeInfo.timeSigNumerator = 4;
    mTimeInfo.timeSigDenominator = 4;
    mTimeInfo.flags = kVstTempoValid | kVstNanosValid;
}

VSTEffectBase::~VSTEffectBase() = default;

PluginPath VSTEffectBase::GetPath() const
{
    return mPath;
}

ComponentInterfaceSymbol VSTEffectBase::GetSymbol() const
{
    return VSTWrapper::GetSymbol();
}

VendorSymbol VSTEffectBase::GetVendor() const
{
    return { mVendor };
}

wxString VSTEffectBase::GetVersion() const
{
    wxString version;

    bool skipping = true;
    for (int i = 0, s = 0; i < 4; i++, s += 8) {
        int dig = (mVersion >> s) & 0xff;
        if (dig != 0 || !skipping) {
            version += !skipping ? wxT(".") : wxT("");
            version += wxString::Format(wxT("%d"), dig);
            skipping = false;
        }
    }

    return version;
}

TranslatableString VSTEffectBase::GetDescription() const
{
    // VST does have a product string opcode and some effects return a short
    // description, but most do not or they just return the name again.  So,
    // try to provide some sort of useful information.
    return XO("Audio In: %d, Audio Out: %d").Format(mAudioIns, mAudioOuts);
}

// ============================================================================
// EffectDefinitionInterface Implementation
// ============================================================================

EffectType VSTEffectBase::GetType() const
{
    if (mAudioIns == 0 && mAudioOuts == 0) {
        return EffectTypeTool;
    }

    if (mAudioIns == 0) {
        return EffectTypeGenerate;
    }

    if (mAudioOuts == 0) {
        return EffectTypeAnalyze;
    }

    return EffectTypeProcess;
}

EffectFamilySymbol VSTEffectBase::GetFamily() const
{
    return VSTPLUGINTYPE;
}

bool VSTEffectBase::IsInteractive() const
{
    return mInteractive;
}

bool VSTEffectBase::IsDefault() const
{
    return false;
}

auto VSTEffectBase::RealtimeSupport() const -> RealtimeSince
{
    return RealtimeSince::Always;

    /* return GetType() == EffectTypeProcess
       ? RealtimeSince::Always
       : RealtimeSince::Never; */
}

bool VSTEffectBase::SupportsAutomation() const
{
    return mAutomatable;
}

bool VSTEffectBase::InitializePlugin()
{
    if (!mAEffect) {
        Load();
    }

    if (!mAEffect) {
        return false;
    }

    return true;
}

std::shared_ptr<EffectInstance> VSTEffectBase::MakeInstance() const
{
    int userBlockSize;
    GetConfig(*this, PluginSettings::Shared, wxT("Options"),
              wxT("BufferSize"), userBlockSize, 8192);
    size_t userBlockSizeC = std::max(1, userBlockSize);
    bool useLatency;
    GetConfig(*this, PluginSettings::Shared, wxT("Options"),
              wxT("UseLatency"), useLatency, true);

    return std::make_shared<VSTInstance>(
        *this, mPath, userBlockSizeC, userBlockSizeC, useLatency);
}

bool VSTEffectBase::SaveSettings(const EffectSettings& settings, CommandParameters& parms) const
{
    const VSTSettings& vstSettings = GetSettings(settings);

    for (const auto& item : vstSettings.mParamsMap) {
        if (item.second) {
            const auto& name  =   item.first;
            const auto& value = *(item.second);

            if (!parms.Write(name, value)) {
                return false;
            }
        }
    }

    return true;
}

bool VSTEffectBase::LoadSettings(const CommandParameters& parms, EffectSettings& settings) const
{
    VSTSettings& vstSettings = GetSettings(settings);

    long index{};
    wxString key;
    double value = 0.0;
    if (parms.GetFirstEntry(key, index)) {
        do{
            if (parms.Read(key, &value)) {
                auto& map = vstSettings.mParamsMap;
                auto iter = map.find(key);
                if (iter != map.end()) {
                    if (iter->second) {
                        // Should be guaranteed by MakeSettings
                        iter->second = value;
                    } else {
                        assert(false);
                    }
                } else {
                    // Unknown parameter name in the file
                    return false;
                }
            }
        } while (parms.GetNextEntry(key, index));
    }

    // Loads key-value pairs only from a config file -- no chunk
    vstSettings.mChunk.resize(0);
    vstSettings.mVersion   = VSTWrapper::mVersion;
    vstSettings.mUniqueID  = VSTWrapper::mAEffect->uniqueID;
    vstSettings.mNumParams = VSTWrapper::mAEffect->numParams;

    return true;
}

RegistryPaths VSTEffectBase::GetFactoryPresets() const
{
    RegistryPaths progs;

    // Some plugins, like Guitar Rig 5, only report 128 programs while they have hundreds.  While
    // I was able to come up with a hack in the Guitar Rig case to gather all of the program names
    // it would not let me set a program outside of the first 128.
    if (mVstVersion >= 2) {
        for (int i = 0; i < mAEffect->numPrograms; i++) {
            progs.push_back(GetString(effGetProgramNameIndexed, i));
        }
    }

    return progs;
}

OptionalMessage
VSTEffectBase::LoadFactoryPreset(int id, EffectSettings& settings) const
{
    // To do: externalize state so const_cast isn't needed
    bool loadOK = const_cast<VSTEffectBase*>(this)->DoLoadFactoryPreset(id)
                  && FetchSettings(GetSettings(settings));
    if (!loadOK) {
        return {}
    }
    return MakeMessageFS(
        VSTInstance::GetSettings(settings));
}

bool VSTEffectBase::DoLoadFactoryPreset(int id)
{
    callSetProgram(id);

    return true;
}

bool VSTEffectBase::CanExportPresets() const
{
    return true;
}

bool VSTEffectBase::HasOptions() const
{
    return true;
}

std::vector<int> VSTEffectBase::GetEffectIDs()
{
    std::vector<int> effectIDs;

    // Are we a shell?
    if (mVstVersion >= 2 && (VstPlugCategory)callDispatcher(effGetPlugCategory, 0, 0, NULL, 0) == kPlugCategShell) {
        char name[64];
        int effectID;

        effectID = (int)callDispatcher(effShellGetNextPlugin, 0, 0, &name, 0);
        while (effectID)
        {
            effectIDs.push_back(effectID);
            effectID = (int)callDispatcher(effShellGetNextPlugin, 0, 0, &name, 0);
        }
    }

    return effectIDs;
}

OptionalMessage VSTEffectBase::LoadUserPreset(
    const RegistryPath& group, EffectSettings& settings) const
{
    wxString value;

    auto info = GetChunkInfo();

    GetConfig(*this, PluginSettings::Private, group, wxT("UniqueID"),
              info.pluginUniqueID, info.pluginUniqueID);
    GetConfig(*this, PluginSettings::Private, group, wxT("Version"),
              info.pluginVersion, info.pluginVersion);
    GetConfig(*this, PluginSettings::Private, group, wxT("Elements"),
              info.numElements, info.numElements);

    if (!IsCompatible(info)) {
        return {};
    }

    if (GetConfig(*this,
                  PluginSettings::Private, group, wxT("Chunk"), value, wxEmptyString)) {
        ArrayOf<char> buf{ value.length() / 4 * 3 };

        int len = Base64::Decode(value, buf.get());
        if (len) {
            callSetChunk(true, len, buf.get(), &info);
            if (!FetchSettings(GetSettings(settings))) {
                return {}
            }
        }

        return MakeMessageFS(
            VSTInstance::GetSettings(settings));
    }

    wxString parms;
    if (!GetConfig(*this,
                   PluginSettings::Private, group, wxT("Parameters"), parms, wxEmptyString)) {
        return {};
    }

    CommandParameters eap;
    if (!eap.SetParameters(parms)) {
        return {};
    }

    const bool loadOK = LoadSettings(eap, settings)
                        && FetchSettings(GetSettings(settings));
    if (!loadOK) {
        return {}
    }

    return MakeMessageFS(
        VSTInstance::GetSettings(settings));
}

bool VSTEffectBase::SaveUserPreset(
    const RegistryPath& group, const EffectSettings& settings) const
{
    const auto& vstSettings = GetSettings(settings);

    if (!StoreSettings(vstSettings)) {
        return false;
    }

    SetConfig(*this, PluginSettings::Private, group, wxT("UniqueID"), vstSettings.mUniqueID);
    SetConfig(*this, PluginSettings::Private, group, wxT("Version"),  vstSettings.mVersion);
    SetConfig(*this, PluginSettings::Private, group, wxT("Elements"), vstSettings.mNumParams);

    if (mAEffect->flags & effFlagsProgramChunks) {
        void* chunk = NULL;
        int clen = (int)constCallDispatcher(effGetChunk, 1, 0, &chunk, 0.0);
        if (clen <= 0) {
            return false;
        }

        SetConfig(*this, PluginSettings::Private, group, wxT("Chunk"),
                  Base64::Encode(chunk, clen));
        return true;
    }

    CommandParameters eap;
    if (!SaveSettings(settings, eap)) {
        return false;
    }

    wxString parms;
    if (!eap.GetParameters(parms)) {
        return false;
    }

    return SetConfig(*this, PluginSettings::Private,
                     group, wxT("Parameters"), parms);
}

EffectSettings VSTEffectBase::MakeSettings() const
{
    VSTSettings settings;
    FetchSettings(settings);
    return EffectSettings::Make<VSTSettings>(std::move(settings));
}
